﻿using CleanArchitecture.Core.Events;
using CleanArchitecture.Core.Handlers;
using CleanArchitecture.Core.Interfaces;
using CleanArchitecture.Core.Interfaces.Cache;
using CleanArchitecture.Infrastructure.Configuration;
using CleanArchitecture.Infrastructure.Entities;
using CleanArchitecture.Infrastructure.Entities.AdminUsers;
using CleanArchitecture.Infrastructure.Entities.Common;
using CleanArchitecture.Infrastructure.Entities.Settings;
using CleanArchitecture.Infrastructure.Entities.System.DataDic;
using CleanArchitecture.Infrastructure.Interfaces;
using CleanArchitecture.Infrastructure.Interfaces.AdminUsers;
using CleanArchitecture.Infrastructure.Models.AdminUsers;
using CleanArchitecture.Infrastructure.Services.DatabaseProvider;
using System;
using System.Collections.Generic;
using System.Linq;

namespace CleanArchitecture.Infrastructure.Services.AdminUsers
{
    public partial class AdminUserService : IAdminUserService
    {
        #region Fields
        private readonly IRepository<AdminUser> _adminuserRepository;
        private readonly IRepository<AdminUserPassword> _passwordRepository;
        private readonly IRepository<AdminRole> _adminroleRepository;
        private readonly IRepository<AdminUserAdminRoleMapping> _userrolemappingRepository;        private readonly IRepository<SystemDataDicTree> _systemdatadictreeRepository;
        private readonly IDbContext _dbContext;
        private readonly IDataProvider _dataProvider;
        private readonly IDomainEventDispatcher _eventDispatcher;
        private readonly AdminUserSettings _adminuserSettings;
        private readonly IStaticCacheManager _cacheManager;
        #endregion

        #region Ctor
        public AdminUserService(IRepository<AdminUser> adminuserRepo,
            IRepository<AdminUserPassword> passwordRepo,
            IRepository<AdminRole> adminroleRepo,
            IRepository<SystemDataDicTree> systemdatadictreeRepo,
            IRepository<AdminUserAdminRoleMapping> userrolemappingRepo,
            IDbContext dbContext,
            IDataProvider dataProvider,
            IDomainEventDispatcher eventDispatcher,
            AdminUserSettings adminuserSettings,
            IStaticCacheManager cacheManager)
        {
            this._adminuserRepository = adminuserRepo;
            this._passwordRepository = passwordRepo;
            this._adminroleRepository = adminroleRepo;
            this._systemdatadictreeRepository = systemdatadictreeRepo;
            this._userrolemappingRepository = userrolemappingRepo;
            this._dbContext = dbContext;
            this._dataProvider = dataProvider;
            this._eventDispatcher = eventDispatcher;
            this._adminuserSettings = adminuserSettings;
            this._cacheManager = cacheManager;
        }
        #endregion

        #region Methods
        public virtual IList<AdminRole> GetAdminUserRolesByUserId(int userId)
        {
            if (userId <= 0) return null;
            //SELECT r.* FROM
            //adminuser_adminrole_mapping AS mp
            //LEFT JOIN admin_role AS r ON mp.Fk_AdminRoleId = r.Id
            //WHERE mp.Fk_AdminUserId = 1 AND r.IsActive = 1;
            var query = from mp in _userrolemappingRepository.TableNoTracking.Where(m => m.FKAdminUserId == userId)
                        join role in _adminroleRepository.TableNoTracking.Where(d => d.IsActive == EntityEnumConfig.YesorNo.YES)
                        on mp.FKAdminRoleId equals role.Id into tempinnerjoin
                        from j in tempinnerjoin.DefaultIfEmpty()
                        select j;
            return query.ToList();
        }

        public virtual void UpdateAdminUser(AdminUser adminuser)
        {
            if (adminuser == null)
                throw new ArgumentNullException(nameof(adminuser));

            if (adminuser.AdminuserAdminroleMappings.Count > 0)
                DeleteAdminuserRoleMappings(adminuser.Id);
            _adminuserRepository.Update(adminuser, new string[] { nameof(adminuser.CreatedOnTimeUtc) });

            //event notification
            if (!string.IsNullOrEmpty(adminuser.SysLogMsg))
                _eventDispatcher.Dispatch(new EntityUpdatedEvent<AdminUser>(adminuser));
        }
        //https://github.com/mysql-net/MySqlConnector/issues/231
        protected void DeleteAdminuserRoleMappings(int uid)
        {
            //prepare parameters
            var pUserId = _dataProvider.GetInt32Parameter("UserId", uid);
            //var pTotalRecordsDeleted = _dataProvider.GetOutputInt32Parameter("TotalRecordsDeleted");

            //invoke stored procedure
            _dbContext.ExecuteSqlCommand("CALL DeleteAdminuserRoleMappings(@UserId)",
                false, null, pUserId);
        }
        public virtual void InsertAdminUser(AdminUser adminuser)
        {
            if (adminuser == null)
                throw new ArgumentNullException(nameof(adminuser));

            _adminuserRepository.Insert(adminuser);

            //event notification
            if (!string.IsNullOrEmpty(adminuser.SysLogMsg))
                _eventDispatcher.Dispatch(new EntityUpdatedEvent<AdminUser>(adminuser));
        }

        public virtual AdminUser GetAdminUserById(int id)
        {
            if (id <= 0) return null;
            return _adminuserRepository.GetById(id);
        }

        public virtual AdminUser GetAdminUserByEmail(string email)
        {
            if (string.IsNullOrWhiteSpace(email))
                return null;

            var query = from u in _adminuserRepository.Table
                        orderby u.Id
                        where u.Email == email && u.IsDeleted == EntityEnumConfig.DeleteState.Active
                        select u;
            var user = query.FirstOrDefault();
            return user;
        }

        public virtual AdminUser GetAdminUserByLoginname(string loginname)
        {
            if (string.IsNullOrWhiteSpace(loginname))
                return null;

            var query = from u in _adminuserRepository.Table
                        orderby u.Id
                        where u.UserLoginName == loginname && u.IsDeleted == EntityEnumConfig.DeleteState.Active
                        select u;
            var user = query.FirstOrDefault();
            return user;
        }

        public virtual AdminUser GetAdminUserByMobile(string mobile)
        {
            if (string.IsNullOrWhiteSpace(mobile))
                return null;

            var query = from u in _adminuserRepository.Table
                        orderby u.Id
                        where u.Mobile == mobile && u.IsDeleted == EntityEnumConfig.DeleteState.Active
                        select u;
            var user = query.FirstOrDefault();
            return user;
        }

        public IViewPagedList<AdminUser> GetAdminUsers(int pageIndex = 0, int pageSize = int.MaxValue)
        {
            var query = _adminuserRepository.Table.Where(u => u.UserLoginName != "BuildInSystemTask" && u.IsDeleted == EntityEnumConfig.DeleteState.Active)
                                            .OrderBy(u => u.CreatedOnTimeUtc);
            var pagedUsers = new ViewPagedList<AdminUser>(query, pageIndex, pageSize);
            return pagedUsers;
        }
        #endregion

        #region Password
        public virtual AdminUserPassword GetCurrentPassword(int userId)
        {
            if (userId == 0)
                return null;

            //filter by AdminUser Id
            return _passwordRepository.Table.FirstOrDefault(password => password.FkUserId == userId);
        }

        /// <summary>
        /// Insert a customer password
        /// </summary>
        /// <param name="customerPassword">Customer password</param>
        public virtual void InsertAdminUserPassword(AdminUserPassword Password)
        {
            if (Password == null)
                throw new ArgumentNullException(nameof(Password));

            _passwordRepository.Insert(Password);

            //event notification
            if (!string.IsNullOrEmpty(Password.SysLogMsg))
                _eventDispatcher.Dispatch(new EntityInsertedEvent<AdminUserPassword>(Password));
        }

        /// <summary>
        /// Update a customer password
        /// </summary>
        /// <param name="customerPassword">Customer password</param>
        public virtual void UpdateAdminUserPassword(AdminUserPassword Password)
        {
            if (Password == null)
                throw new ArgumentNullException(nameof(Password));

            _passwordRepository.Update(Password);

            //event notification
            if (!string.IsNullOrEmpty(Password.SysLogMsg))
                _eventDispatcher.Dispatch(new EntityUpdatedEvent<AdminUserPassword>(Password));
        }

        /// <summary>
        /// Check whether AdminUser password is expired 
        /// </summary>
        /// <param name="AdminUser">AdminUser</param>
        /// <returns>True if password is expired; otherwise false</returns>
        public virtual bool PasswordIsExpired(AdminUser user)
        {
            if (user == null)
                throw new ArgumentNullException(nameof(user));

            //系统管理员不设置密码过期
            var userRoles = user.GetAdminRoles();
            if (userRoles.Any(r => r.IsAdmin == EntityEnumConfig.YesorNo.YES))
                return false;

            //setting disabled for all
            if (_adminuserSettings.PasswordLifetime == 0)
                return false;

            //cache result between HTTP requests
            var cacheKey = string.Format(DefaultCacheConfig.AdminUserPasswordLifetimeCacheKey, user.Id);

            //get current password usage time
            var currentLifetime = _cacheManager.Get(cacheKey, () =>
            {
                var userPassword = GetCurrentPassword(user.Id);
                //password is not found, so return max value to force customer to change password
                if (userPassword == null)
                    return int.MaxValue;

                return (DateTime.UtcNow - userPassword.CreatedOnTimeUtc).Days;
            });

            return currentLifetime >= _adminuserSettings.PasswordLifetime;
        }
        /// <summary>
        /// DavidLee 2020/02/17 为避免LazyLoading造成NullReferenceException
        /// 将对用户角色的查询进行分开操作，当不需要用到user-role 及 role-systemmoulde映射关系时
        /// 只查询无映射关系的用户角色
        /// </summary>
        /// <returns></returns>
        public virtual IList<AdminRoleModel> GetAdminRoleModelsAll()
        {
            var cacheKey = DefaultCacheConfig.AdminRolesAllCacheKey;
            return _cacheManager.Get(cacheKey, () =>
            {
                //SELECT r.Id, r.RoleName, r.IsAdmin, r.IsActive, r.DisplayOrder, o.DisplayName
                //FROM admin_role as r
                //LEFT JOIN sys_datadictree as o ON r.FK_OrganizeId = o.Id
                //WHERE r.IsActive = 1
                //ORDER BY r.DisplayOrder,r.FK_OrganizeId
                var query = from role in _adminroleRepository.Table where role.IsActive == EntityEnumConfig.YesorNo.YES
                            join dictree in _systemdatadictreeRepository.Table.Where(d => d.IsActive == EntityEnumConfig.YesorNo.YES && d.GroupType == EntityEnumConfig.DataDicGroup.Oragnize)
                            on role.FKOrganizeId equals dictree.Id into tempinnerjoin
                            from j in tempinnerjoin.DefaultIfEmpty()
                            orderby role.FKOrganizeId, role.DisplayOrder
                            select new AdminRoleModel
                            {
                                Id = role.Id,
                                RoleName = role.RoleName,
                                IsActive = role.IsActive,
                                IsAdmin = role.IsAdmin,
                                DisplayOrder = role.DisplayOrder,
                                FKOrganizeId = j.Id,
                                OranizeName = string.IsNullOrEmpty(j.DisplayName) ? j.DataDicTreeName : j.DisplayName
                            };
                return query.ToList();
            });
        }

        public virtual IList<AdminRole> GetAdminRolesAll()
        {
            var query = _adminroleRepository.Table;
            query = query.Where(role => role.IsActive == EntityEnumConfig.YesorNo.YES);
            query = query.OrderBy(module => module.FKOrganizeId).ThenBy(module => module.DisplayOrder);
            return query.ToList();
        }

        public virtual AdminRole GetAdminRoleById(int id)
        {
            if (id == 0) return null;
            return _adminroleRepository.GetById(id);
        }
        /// <summary>
        /// 检查用户-角色对应表，如果被删除用户角色有对应的用户只关联唯一用户角色，则不允许删除角色
        /// </summary>
        /// <param name="roleId"></param>
        /// <returns></returns>
        public virtual bool CheckAdminRoleBeforeDelete(int roleId)
        {
            string selectSQL = "SELECT COUNT(Fk_AdminRoleId) AS RoleCount, Fk_AdminUserId AS UserId";
            selectSQL += " FROM adminuser_adminrole_mapping";
            selectSQL += $" WHERE Fk_AdminUserId IN(SELECT Fk_AdminUserId FROM adminuser_adminrole_mapping WHERE Fk_AdminRoleId = {roleId})";
            selectSQL += " GROUP BY Fk_AdminUserId";

            var result = _dbContext.QueryFromSql<UserRoleMappingQuery>(selectSQL).ToList();
            if (result == null) return false;
            return result.Any(list => list.RoleCount == 1);
        }

        public virtual void UpdateAdminRole(AdminRole role, IList<string> excludeProperties = null)
        {
            if (role == null)
                throw new ArgumentNullException(nameof(role));
            if (excludeProperties != null && excludeProperties.Count > 0)
                _adminroleRepository.Update(role, excludeProperties.ToArray());
            else _adminroleRepository.Update(role);
            if (string.IsNullOrEmpty(role.SysLogMsg))
                _eventDispatcher.Dispatch(new EntityUpdatedEvent<AdminRole>(role));
            _cacheManager.Remove(DefaultCacheConfig.AdminRolesAllCacheKey);
            //DavidLee 2020/0731 增加角色更新后系统/栏目权限控制的缓存更新
            string cacheKey = string.Format(DefaultCacheConfig.SystemModuleAuthorizedCacheKey, role.Id);
            _cacheManager.Remove(cacheKey);
            cacheKey = DefaultCacheConfig.ColumnAuthorizedCacheKey;
            cacheKey = cacheKey.Substring(0, cacheKey.IndexOf("."));
            _cacheManager.RemoveByPrefix(cacheKey);
        }
        public virtual void InsertAdminRole(AdminRole role)
        {
            if (role == null)
                throw new ArgumentNullException(nameof(role));

            _adminroleRepository.Insert(role);

            //event notification
            if (!string.IsNullOrEmpty(role.SysLogMsg))
                _eventDispatcher.Dispatch(new EntityInsertedEvent<AdminRole>(role));
            _cacheManager.Remove(DefaultCacheConfig.AdminRolesAllCacheKey);
        }
        #endregion
    }

    /// <summary>
    /// AdminRole (for caching) to sovle lazy loading
    /// </summary>
    //[Serializable]
    ////Entity Framework will assume that any class that inherits from a POCO class that is mapped to a table on the database requires a Discriminator column
    ////That's why we have to add [NotMapped] as an attribute of the derived class.
    //[NotMapped]
    //public class AdminRoleForCaching : AdminRole, IEntityForCaching
    //{
    //    /// <summary>
    //    /// Ctor
    //    /// </summary>
    //    /// <param name="l">AdminRole to copy</param>
    //    public AdminRoleForCaching(AdminRole r)
    //    {
    //        Id = r.Id;
    //        RoleName = r.RoleName;
    //        FKOrganizeId = r.FKOrganizeId;
    //        IsActive = r.IsActive;
    //        IsAdmin = r.IsAdmin;
    //        DisplayOrder = r.DisplayOrder;
    //        CreatedOnTimeUtc = r.CreatedOnTimeUtc;
    //        //Organize = r.Organize;
    //        AdminroleSystemModuleMappings = r.AdminroleSystemModuleMappings;
    //    }
    //}
}
